﻿using System;

namespace Patterns.AbstractFactory {

    public static class AbstractExample {

        public static void Run() {
            // Abstract factory #1
            AbstractFactory factory1 = new ConcreteFactory1();
            Client c1 = new Client(factory1);
            c1.Run();

            // Abstract factory #2
            AbstractFactory factory2 = new ConcreteFactory2();
            Client c2 = new Client(factory2);
            c2.Run();
        }

        /// <summary>
        /// Declares an interface for operations that create
        /// abstract product.
        /// </summary>
        private abstract class AbstractFactory {
            public abstract AbstractProductA CreateProductA();
            public abstract AbstractProductB CreateProductB();
        }

        /// <summary>
        /// Implements the operations to create concrete product objects.
        /// </summary>
        private class ConcreteFactory1 : AbstractFactory {
            public override AbstractProductA CreateProductA() {
                return new ProductA1();
            }
            public override AbstractProductB CreateProductB() {
                return new ProductB1();
            }
        }

        /// <summary>
        /// Implements the operations to create concrete product objects.
        /// </summary>
        private class ConcreteFactory2 : AbstractFactory {
            public override AbstractProductA CreateProductA() {
                return new ProductA2();
            }
            public override AbstractProductB CreateProductB() {
                return new ProductB2();
            }
        }

        /// <summary>
        /// Declares an interface for a type of product object.
        /// </summary>
        private abstract class AbstractProductA {
        }

        /// <summary>
        /// Declares an interface for a type of product object.
        /// </summary>
        private abstract class AbstractProductB {
            public abstract void Interact(AbstractProductA a);
        }

        /// <summary>
        /// Defines a product object to be created by the corresponding
        /// concrete factory.
        /// Implements the AbstractProduct interface.
        /// </summary>
        private class ProductA1 : AbstractProductA {
        }

        /// <summary>
        /// Defines a product object to be created by the corresponding
        /// concrete factory.
        /// Implements the AbstractProduct interface.
        /// </summary>
        private class ProductB1 : AbstractProductB {
            public override void Interact(AbstractProductA a) {
                Console.WriteLine(this.GetType().Name +
                  " interacts with " + a.GetType().Name);
            }
        }

        /// <summary>
        /// Defines a product object to be created by the corresponding
        /// concrete factory.
        /// Implements the AbstractProduct interface.
        /// </summary>
        private class ProductA2 : AbstractProductA {
        }

        /// <summary>
        /// Defines a product object to be created by the corresponding
        /// concrete factory.
        /// Implements the AbstractProduct interface.
        /// </summary>
        private class ProductB2 : AbstractProductB {
            public override void Interact(AbstractProductA a) {
                Console.WriteLine(this.GetType().Name +
                  " interacts with " + a.GetType().Name);
            }
        }

        /// <summary>
        /// Uses interfaces declared by AbstractFactory and
        /// AbstractProduct classes.
        /// </summary>
        private class Client {
            private AbstractProductA AbstractProductA;
            private AbstractProductB AbstractProductB;

            // Constructor
            public Client(AbstractFactory factory) {
                AbstractProductB = factory.CreateProductB();
                AbstractProductA = factory.CreateProductA();
            }

            public void Run() {
                AbstractProductB.Interact(AbstractProductA);
            }
        }
    }
}
